from flask import Flask, render_template, request, redirect, url_for, send_from_directory, flash
import os
import uuid
import urllib.parse
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 500 * 1024 * 1024  # 限制上传文件大小为500MB
app.secret_key = os.urandom(24)  # 使用随机生成的密钥，提高安全性
# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)

# 允许所有文件类型  
def allowed_file(filename):
    """所有文件类型均允许上传"""
    return True

def get_human_readable_size(size_bytes):
    """将字节大小转换为人类可读的格式"""
    if size_bytes < 1024:
        return f"{size_bytes} B"
    elif size_bytes < 1024 * 1024:
        return f"{size_bytes / 1024:.2f} KB"
    elif size_bytes < 1024 * 1024 * 1024:
        return f"{size_bytes / (1024 * 1024):.2f} MB"
    else:
        return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"

@app.route('/favicon.ico')
def favicon():
    return send_from_directory(os.path.join(app.root_path, 'static'),
                              'favicon.ico', mimetype='image/vnd.microsoft.icon')

@app.route('/')
@app.route('/<path:folder_path>')
def index(folder_path=None):
    # 确定当前目录路径
    if folder_path:
        current_dir = os.path.join(app.config['UPLOAD_FOLDER'], folder_path)
        # 安全检查：确保路径不会超出上传目录
        # if not os.path.normpath(current_dir).startswith(os.path.normpath(app.config['UPLOAD_FOLDER'])):
        #     flash('无效的目录路径', 'error')
        #     return redirect(url_for('index'))
    else:
        current_dir = app.config['UPLOAD_FOLDER']
        folder_path = ''
    
    # 如果目录不存在，提示无效的目录路径
    if not os.path.exists(current_dir):
        flash('无效的目录路径', 'error')
        return redirect(url_for('index'))
        # os.makedirs(current_dir, exist_ok=True)
    
    # 准备导航信息
    nav_paths = []
    if folder_path:
        parts = folder_path.split('/')
        for i in range(len(parts)):
            path_part = '/'.join(parts[:i+1])
            nav_paths.append({
                'name': parts[i],
                'path': path_part
            })
    # 获取文件和文件夹列表
    items = []
    
    # 读取文件名映射
    # filename_mappings = {}
    # mapping_file = os.path.join(current_dir, '.filename_mapping.txt')
    # if os.path.exists(mapping_file):
    #     try:
    #         with open(mapping_file, 'r', encoding='utf-8') as f:
    #             for line in f:
    #                 if ':' in line:
    #                     safe_name, orig_name = line.strip().split(':', 1)
    #                     filename_mappings[safe_name] = orig_name
    #     except Exception as e:
    #         app.logger.error(f'读取文件名映射时出错: {str(e)}')
    for item_name in os.listdir(current_dir):
        # 跳过映射文件
        # if item_name == '.filename_mapping.txt':
        #     continue
            
        item_path = os.path.join(current_dir, item_name)
        is_file = os.path.isfile(item_path)
        
        if is_file:
            file_size = os.path.getsize(item_path)
            size_str = get_human_readable_size(file_size)
            
            # 使用原始文件名显示，但下载路径使用实际存储的文件名
            # display_name = filename_mappings.get(item_name, item_name)
            
            items.append({
                'name': item_name,
                'is_file': True,
                'size': size_str,
                'path': url_for('download_file', folder_path=folder_path, filename=item_name) if is_file else None
            })
        else:
            # 这是一个文件夹
            items.append({
                'name': item_name,
                'is_file': False,
                'size': '文件夹',
                'path': url_for('index', folder_path=os.path.join(folder_path, item_name) if folder_path else item_name)
            })
    
    # 按类型排序：文件夹在前，文件在后
    items.sort(key=lambda x: (x['is_file'], x['name']))
    
    return render_template('index.html', 
                           items=items, 
                           current_path=folder_path,
                           nav_paths=nav_paths,
                           parent_path=os.path.dirname(folder_path) if folder_path else None)

# 设置最大文件大小为600MB
MAX_CONTENT_LENGTH = 600 * 1024 * 1024

@app.route('/upload', methods=['GET', 'POST'])
@app.route('/upload/<path:folder_path>', methods=['GET', 'POST'])
def upload_file(folder_path=None):
    if request.method == 'GET':
        return redirect(url_for('index', folder_path=folder_path))
    
    # 确定上传目标目录
    if folder_path:
        target_dir = os.path.join(app.config['UPLOAD_FOLDER'], folder_path)
        # 安全检查：确保路径不会超出上传目录
        if not os.path.normpath(target_dir).startswith(os.path.normpath(app.config['UPLOAD_FOLDER'])):
            flash('无效的目录路径', 'error')
            return redirect(url_for('index'))
    else:
        target_dir = app.config['UPLOAD_FOLDER']
        folder_path = ''
    
    # 确保目标目录存在
    if not os.path.exists(target_dir):
        os.makedirs(target_dir, exist_ok=True)
        
    if 'file' not in request.files:
        flash('没有选择文件', 'error')
        return redirect(url_for('index', folder_path=folder_path))
    
    file = request.files['file']
    
    if file.filename == '':
        flash('没有选择文件', 'error')
        return redirect(url_for('index', folder_path=folder_path))
    
    if file:
        try:
            # 检查文件大小
            file.seek(0, os.SEEK_END)
            size = file.tell()
            file.seek(0)
            if size > MAX_CONTENT_LENGTH:
                app.logger.error(f'文件大小超过限制：{size} bytes')
                if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                    return {'success': False, 'message': '文件大小不能超过500MB'}, 413
                flash('文件大小不能超过500MB', 'error')
                return redirect(url_for('index', folder_path=folder_path))

            # 保存原始文件名用于显示
            filename = file.filename
            # 使用secure_filename处理文件名以确保安全
            # filename = secure_filename(original_filename)
            # print(filename)
            # 如果文件已存在，添加唯一标识符
            # if os.path.exists(os.path.join(target_dir, filename)):
            #     name, extension = os.path.splitext(filename)
            #     filename = f"{name}_{str(uuid.uuid4())[:8]}{extension}"
        except Exception as e:
            app.logger.error(f'文件处理过程中出错: {str(e)}')
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return {'success': False, 'message': f'文件处理错误: {str(e)}'}, 500
            flash(f'文件处理错误: {str(e)}', 'error')
            return redirect(url_for('index', folder_path=folder_path))
        try:
            # 记录开始上传
            app.logger.info(f'开始上传文件 {filename} 到 {target_dir}')
            file_path = os.path.join(target_dir, filename)
            
            # 保存原始文件名和安全文件名的映射关系
            # mapping_file = os.path.join(target_dir, '.filename_mapping.txt')
            # try:
            #     with open(mapping_file, 'a', encoding='utf-8') as f:
            #         f.write(f"{filename}:{original_filename}\n")
            # except Exception as e:
            #     app.logger.error(f'保存文件名映射时出错: {str(e)}')
            
            # 尝试写入文件
            file.save(file_path)
            
            # 验证文件是否成功写入
            if not os.path.exists(file_path):
                raise IOError('文件写入失败，无法在目标位置找到文件')
                
            # 验证文件大小
            actual_size = os.path.getsize(file_path)
            if actual_size == 0:
                os.remove(file_path)
                raise IOError('文件写入失败，文件大小为0')
                
            app.logger.info(f'文件 {filename} 上传成功到 {target_dir}，大小：{actual_size} bytes')
            flash('文件上传成功', 'success')
        except PermissionError:
            app.logger.error(f'目录 {target_dir} 写入权限不足')
            flash('服务器写入权限不足，请联系管理员', 'error')
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return {'success': False, 'message': '服务器权限错误'}, 500
            else:
                return redirect(url_for('index', folder_path=folder_path))
        except Exception as e:
            app.logger.error(f'文件上传失败: {str(e)}')
            flash(f'上传失败: {str(e)}', 'error')
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return {'success': False, 'message': f'上传失败: {str(e)}'}, 500
            else:
                return redirect(url_for('index', folder_path=folder_path))
        
        # 检查是否是AJAX请求
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return {'success': True, 'message': '文件上传成功'}, 200
        else:
            return redirect(url_for('index', folder_path=folder_path))
    else:
        flash('文件上传失败', 'error')
        
        # 检查是否是AJAX请求
        if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
            return {'success': False, 'message': '文件上传失败'}, 400
        else:
            return redirect(url_for('index', folder_path=folder_path))

@app.route('/download/<filename>')
@app.route('/download/<path:folder_path>/<filename>')
def download_file(filename, folder_path=None):
    if folder_path:
        directory = os.path.join(app.config['UPLOAD_FOLDER'], folder_path)
        # 安全检查：确保路径不会超出上传目录
        if not os.path.normpath(directory).startswith(os.path.normpath(app.config['UPLOAD_FOLDER'])):
            flash('无效的文件路径', 'error')
            return redirect(url_for('index'))
    else:
        directory = app.config['UPLOAD_FOLDER']
    
    return send_from_directory(directory, filename, as_attachment=True)

@app.route('/delete/<filename>', methods=['GET', 'POST'])
@app.route('/delete/<path:folder_path>/<filename>', methods=['GET', 'POST'])
def delete_file(filename, folder_path=None):
    if folder_path:
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], folder_path, filename)
        # 安全检查：确保路径不会超出上传目录
        if not os.path.normpath(file_path).startswith(os.path.normpath(app.config['UPLOAD_FOLDER'])):
            flash('无效的文件路径', 'error')
            return redirect(url_for('index'))
    else:
        file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    
    if os.path.exists(file_path):
        if os.path.isfile(file_path):
            os.remove(file_path)
            flash('文件删除成功', 'success')
        else:
            flash('不能删除文件夹', 'error')
    else:
        flash('文件不存在', 'error')
    
    return redirect(url_for('index', folder_path=folder_path))

# 处理文件大小超出限制的错误
@app.errorhandler(413)
def request_entity_too_large(error):
    flash('文件大小超出限制（最大500MB）', 'error')
    return redirect(url_for('index'))



if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0', port=5000)